package com.googlecode.cswish.struts;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.RequestUtils;
import org.springframework.stereotype.Service;

import com.googlecode.cswish.Config;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.util.FrameConstant;
import com.googlecode.cswish.util.Functions;

/**
 * Routing table
 * 
 * @author jerry.feng Date: 2008-9-25
 */
@Service
public class Routing {

	private static final Log logger = LogFactory.getLog(Routing.class);
	
	@Resource
	private BeanIntrospector beanIntrospector;
	
	@Resource
	private Config config;
	
	@Resource
	private Functions functions;
	
	public static int findMethodSeparator(String path) {
		return path.lastIndexOf('.');
	}
	
	public String getMatchPath(String path) {
		Invoker invoker = path2Invoker(path, true);
		if (invoker.isInputMethod()) {
			invoker.methodName = invoker.getSubmitMethod();
		} else {
			invoker.methodName = invoker.getInputMethod();
		}
		String matchPath = '/' + invoker2PathNoExtension(invoker, true) + ".html";		// TODO: USE CONFIG
		return matchPath;
	}
	
	public Invoker action2Invoker(String actionClassName) {
		int index = findMethodSeparator(actionClassName);

		char separator = '.';
		String methodName = null;
		if (index >= 0) {
			separator = actionClassName.charAt(index);
			// first character should be low case
			int endPos = actionClassName.length() - FrameConstant.ACTION_PACKAGE_SUFFIX.length();
			if (endPos > index) {
				methodName = Character.toLowerCase(actionClassName.charAt(index + 1)) + 
					actionClassName.substring(index + 2, endPos);
			}
		}
		if (methodName == null) {
			separator = '.';
			methodName = "execute";
		}
		return new Invoker(actionClassName, separator, methodName, ContextHolder.get().getSiteName());
	}
	
	public Invoker path2Invoker(String path, boolean pathContainSite) {
		if (pathContainSite && !config.isEnableSitePath()) {
			pathContainSite = false;			// saas is disabled, the path doesn't contain the site information
		}
		
		String extension = functions.getActionExtension();
		if (extension != null && path.endsWith(extension)) {
			path = path.substring(0, path.length() - functions.getActionExtension().length() - 1);
		} else if (path.endsWith("dec")) {
			path = path.substring(0, path.length() - 4);
		}
		int index = findMethodSeparator(path);
		
		String methodName;
		char separator;
		if (index > 0) {
			methodName = path.substring(index + 1);
			if (methodName.isEmpty()) {
				methodName = "execute";
			}
			separator = path.charAt(index);
			// class name can't contain '/'
			if (separator == '/') {
				separator = '\\';
			}
		} else {
			methodName = "execute";
			index = path.length();
			separator = '.';
		}
		
		int pathBegin = 0;
		if (path.length() > 0 && path.charAt(0) == '/') {
			pathBegin = 1;
		}
		
		// site information
		int siteEnd;
		String site = null;
		if (pathContainSite) {
			siteEnd = path.indexOf('/', pathBegin);
			if (siteEnd > 0) {
				site = path.substring(pathBegin, siteEnd);
			} else {
				siteEnd = pathBegin - 1;
			}
		} else {
			site = ContextHolder.get().getSiteName();
			siteEnd = pathBegin - 1;
		}
		
		String className = path.substring(siteEnd + 1, index).replace('/', '.');
		if (className.length() > 0 && className.charAt(className.length() - 1) == separator) {
			className = className + "index";
		}
		String actionClassName = FrameConstant.PACKAGE_PREFIX + className + separator 
			+ Character.toUpperCase(methodName.charAt(0)) + methodName.substring(1) 
			+ FrameConstant.ACTION_PACKAGE_SUFFIX;
		Invoker invoker = new Invoker(actionClassName, separator, methodName, site);
		return invoker;
	}
	
	/**
	 * NOTICE: the path don't begin with '/'
	 */
	public String invoker2PathNoExtension(Invoker invoker, boolean withSite) {
		if (withSite && !config.isEnableSitePath()) {
			withSite = false;
		}
		
		String simpleName = beanIntrospector.getSimpleName(invoker.actionName);
		return invoker2PathNoExtension(simpleName, invoker.separator, invoker.methodName,
				withSite ? invoker.siteName : null);
	}
	
	// see Functions.linkNoBase
	public static String invoker2PathNoExtension(String simpleName, char separator,
			String methodName, boolean withSite) {
		if (withSite && !Config.getInstance().isEnableSitePath()) {
			withSite = false;
		}
		
		if (withSite) {
			return invoker2PathNoExtension(simpleName, separator, methodName, 
					ContextHolder.get().getSiteName());
		} else {
			return invoker2PathNoExtension(simpleName, separator, methodName, null);
		}
	}
	
	/**
	 * FIXME: rename it to invoker2PathNoExtension
	 * 
	 * @param simpleName
	 * @param separator
	 * @param methodName
	 * @return
	 */
	public static String invoker2PathNoExtension(String simpleName, char separator,
			String methodName, String siteName) {
		if (siteName != null && !Config.getInstance().isEnableSitePath()) {
			siteName = null;
		}
		
		String path;
		if (siteName != null) {
			path = siteName + '/' + simpleName.replace('.', '/');
		} else {
			path = simpleName.replace('.', '/');
		}
		if (methodName == null) {
			return path;
		} else {
			return path + separator + methodName;
		}
	}
	
	public static Map<String, String[]> getParameters(String path) {
		if (path == null || path.isEmpty()) {
			return null;
		}
		
		int index = path.indexOf('?');
		if (index > 0) {
			path = path.substring(index + 1);
		} else {
			return null;
		}
		
		Map<String, String[]> parameters = new HashMap<String, String[]>();
		String [] keyValues = StringUtils.split(path, '&');
		for (String keyValue : keyValues) {
			int sep = keyValue.indexOf('=');
			String key;
			String value;
			if (sep > 0) {
				key = keyValue.substring(0, sep);
				value = keyValue.substring(sep + 1);
			} else {
				key = keyValue;
				value = "";
			}
			String[] oldValues = parameters.get(key);
			// usually, the old values is null
			if (oldValues == null) {
				parameters.put(key, new String[] {value});
			} else {
				String[] newValues = new String[oldValues.length + 1];
				System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
				newValues[oldValues.length] = value;
				parameters.put(key, newValues);
			}
		}
		return parameters;
	}
	
	public static String getUri(HttpServletRequest request) {
        // handle http dispatcher includes.
        String uri = (String) request
                .getAttribute("javax.servlet.include.servlet_path");
        if (uri != null) {
        	int index = uri.indexOf('?');
			if (index > 0) {
				uri = uri.substring(0, index);
			}
        } else {
        	uri = RequestUtils.getServletPath(request);
        }
        return uri;
    }

	public static String getRequestPathNoSite(String actionPath) {
		if (actionPath.charAt(0) == '/') {
			actionPath = actionPath.substring(1);		// skip the first '/'
		}
		
		if (Config.getInstance().isEnableSitePath()) {
			int index = actionPath.indexOf('/');
			if (index > 0) {
				int begin = actionPath.startsWith("$/") ? 2 : 0;
				return actionPath.substring(actionPath.indexOf('/', begin) + 1);
			} else {
				return null;
			}
		} else {
			int begin = actionPath.startsWith("$/") ? 2 : 0;
			return actionPath.substring(begin);
		}
	}
}